package handler

import (
	"crypto/rand"
	"errors"
	"fmt"
	"math/big"
	"ruoyi-go-echo-ddd/internal/infrastructure/http/request"
	"ruoyi-go-echo-ddd/internal/infrastructure/http/response"
	"ruoyi-go-echo-ddd/internal/infrastructure/utils"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/labstack/echo/v4"
	"github.com/rs/zerolog"
)

// 字符集定义
var (
	digitChars  = "123456789"
	letterChars = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnopqrstuvwxyz"
	mixChars    = digitChars + letterChars
)

const (
	errorCountSuffix = ":err_count"    // 错误计数键后缀
	codePrefix       = "validator:"    // 验证码键前缀
	codeSuffix       = ":code"         // 验证码键后缀
	errorWindow      = time.Minute     // 错误计数时间窗口
	codeExpiration   = 5 * time.Minute // 验证码有效期
)

type ValidatorCodeHandler struct {
	RedisClient *redis.Client
}

func NewValidatorCodeHandler(client *redis.Client) *ValidatorCodeHandler {
	return &ValidatorCodeHandler{
		RedisClient: client,
	}
}

// CodeCreateHandler 生成验证码
// @Summary 生成验证码
// @Description 生成验证码
// @Tags 验证码控制器
// @Produce application/json; charset=UTF-8
// @Param param query request.ValidatorCodeInitReq true "生成验证码参数"
// @Success 200 {object} response.Result{data=request.ValidatorCodeParam} "生成成功"
// @Failure 400 {object} response.Result "参数解析错误"
// @Failure 500 {object} response.Result "服务器错误"
// @Router /global/code/create [get]
func (h *ValidatorCodeHandler) CodeCreateHandler(c echo.Context) error {
	logger := zerolog.DefaultContextLogger
	var param = new(request.ValidatorCodeInitReq)
	if err := BindParam(c, param); err != nil {
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	logger.Info().Msgf("生成的验证码入参===%s", utils.ToJsonFormat(param))

	if param.Type == "" {
		response.NewRespCodeMsg(c, 500, "验证码类型不能为空")
		return errors.New("验证码类型不能为空")
	}

	// 生成验证码
	code, err := GenerateRandomCode(param.Type, param.Length)
	logger.Info().Msgf("生成的验证码===%s", code)

	if err != nil {
		logger.Error().Err(err).Msg("生成验证码错误")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	uuid := utils.ShortUUID()
	cacheKey := codePrefix + uuid + codeSuffix

	result, err := utils.Set(c.Request().Context(), h.RedisClient, cacheKey, code, codeExpiration)
	if err != nil {
		logger.Error().Err(err).Msgf("存储key===%s验证码===%s到redis错误", cacheKey, code)
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	logger.Info().Msgf("存储验证码===%s到redis返回结果===%s", code, result)

	response.NewResponse(c, 200, "生成成功", &request.ValidatorCodeParam{
		Uuid: uuid,
		Code: code,
	})

	return nil
}

// CodeValidatorHandler 校验验证码
// @Summary 校验验证码
// @Description 校验验证码
// @Tags 验证码控制器
// @Produce application/json; charset=UTF-8
// @Param param query request.ValidatorCodeParam true "校验验证码参数"
// @Success 200 {object} response.Result{data=string} "验证码校验成功"
// @Failure 400 {object} response.Result "参数解析错误"
// @Failure 500 {object} response.Result "服务器错误"
// @Router /global/code/validator [get]
func (h *ValidatorCodeHandler) CodeValidatorHandler(c echo.Context) error {
	logger := zerolog.DefaultContextLogger
	var param = new(request.ValidatorCodeParam)
	if err := BindParam(c, param); err != nil {
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	logger.Info().Msgf("校验验证码入参===%s", utils.ToJsonFormat(param))

	if param.Uuid == "" || param.Code == "" {
		response.NewRespCodeMsg(c, 500, "验证码ID与验证码不能为空")
		return errors.New("验证码唯一ID与验证码不能为空")
	}

	// 验证验证码
	if err := ValidatorRandomCode(c, h.RedisClient, param.Uuid, param.Code); err != nil {
		logger.Error().Err(err).Msg("校验失败")
		response.NewRespCodeErr(c, 500, err)
		return err
	}

	response.NewRespCodeMsg(c, 200, "验证成功")
	return nil
}

func GenerateRandomCode(codeType string, length int) (string, error) {
	if length <= 0 {
		length = 4
	}

	var charPool string
	switch codeType {
	case "digit":
		charPool = digitChars
	case "letter":
		charPool = letterChars
	case "mix":
		charPool = mixChars
	default:
		return "", errors.New("invalid code type")
	}

	/* result := make([]byte, length)
	   poolLen := big.NewInt(int64(len(charPool)))

	   for i := range length {
	   	idx, err := rand.Int(rand.Reader, poolLen)
	   	if err != nil {
	   		return "", err
	   	}
	   	result[i] = charPool[idx.Int64()]
	   }

	   return strings.ToUpper(string(result)), nil */
	// 递归生成函数确保mix类型至少有一个数字
	return GenerateMixedCode(length, charPool, codeType == "mix")
}

func ValidatorRandomCode(c echo.Context, client *redis.Client, uuid, code string) error {
	logger := zerolog.DefaultContextLogger

	context := c.Request().Context()
	// 错误计数键
	errCountKey := uuid + errorCountSuffix

	errCount, err := client.Get(context, errCountKey).Int()
	if err != nil {
		if err == redis.Nil {
			errCount = 0 // 键不存在，错误计数为0
		} else {
			logger.Error().Err(err).Msg("获取验证错误次数错误")
			return err
		}
	}

	if errCount >= 2 {
		ttl, err := client.TTL(context, errCountKey).Result()
		if err != nil {
			logger.Error().Err(err).Msg("获取验证最大错误次数错误")
			return err
		}
		return fmt.Errorf("超过最大错误次数，请%v秒后再试", ttl.Seconds())
	}

	// 2. 获取存储的验证码
	codeKey := codePrefix + uuid + codeSuffix
	storedCode, err := utils.Get(context, client, codeKey)
	if err != nil {
		if err == redis.Nil {
			logger.Error().Err(err).Msg("验证码已过期")
			return errors.New("验证码已过期或不存在")
		}
		logger.Error().Err(err).Msg("获取验证码错误")
		return err
	}

	// 3. 比较验证码（注意：生成时转成了大写，所以这里也要转大写）
	if strings.ToUpper(code) != storedCode {
		// 增加错误计数
		newCount := errCount + 1
		// 设置错误计数，如果键不存在则创建，并设置过期时间（错误窗口）
		if err := client.Set(context, errCountKey, newCount, errorWindow).Err(); err != nil {
			logger.Error().Err(err).Msg("更新错误计数失败")
			return errors.New("验证码错误，但更新错误计数失败")
		}

		// 如果错误次数达到3次，则删除验证码（可选）
		if newCount >= 3 {
			// 删除验证码，防止暴力破解
			_, err = utils.Del(context, client, codeKey)
			if err != nil {
				logger.Error().Err(err).Msg("删除验证码失败")
			}
			return errors.New("验证码错误次数过多，验证码已失效")
		}

		return errors.New("验证码错误")
	}

	// 4. 验证成功，删除验证码和错误计数
	/* pipe := client.Pipeline()
	   pipe.Del(context, codeKey)
	   pipe.Del(context, errCountKey)

	   _, err = pipe.Exec(context)
	   if err != nil {
	   	logger.Error().Err(err).Msg("删除旧错误计数验证错误次数错误")
	   	return err
	   } else {
	   	logger.Info().Msg("删除了验证码与验证码错误计数")
	   } */

	count, err := utils.Del(context, client, codeKey, errCountKey)
	if err != nil {
		logger.Error().Err(err).Msg("删除旧错误计数验证错误次数错误")
		return err
	} else {
		logger.Info().Msgf("删除验证码与错误计数返回结果===%d", count)
	}

	return nil
}

// GenerateMixedCode 递归生成验证码，确保混合类型至少包含一个数字
func GenerateMixedCode(length int, charPool string, mustContainDigit bool) (string, error) {
	var maxAttempts = 15 // 最大尝试次数防止无限递归

	// 基础生成逻辑
	generate := func() (string, error) {
		result := make([]byte, length)
		poolLen := big.NewInt(int64(len(charPool)))

		for i := range result {
			idx, err := rand.Int(rand.Reader, poolLen)
			if err != nil {
				return "", err
			}
			result[i] = charPool[idx.Int64()]
		}
		return string(result), nil
	}

	// 尝试生成验证码
	for attempt := 0; attempt < maxAttempts; attempt++ {
		code, err := generate()
		if err != nil {
			return "", err
		}

		zerolog.DefaultContextLogger.Info().Msgf("当前递归调用验证码次数===%d", attempt)

		// 检查是否至少包含一个数字
		if !mustContainDigit || ContainsDigit(code) {
			return strings.ToUpper(code), nil
		}
	}

	// 超过最大尝试次数后返回错误
	return "", errors.New("无法生成带数字的验证码")
}

// ContainsDigit 检查字符串是否包含数字
func ContainsDigit(s string) bool {
	for _, char := range s {
		// 注意digitChars去除了'0'
		if char >= '1' && char <= '9' {
			return true
		}
	}
	return false
}
